/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dcloud.core.client.rdbms.absRdbms;

import com.dcloud.core.client.IClient;
import com.dcloud.core.exception.DtLoaderException;
import com.dcloud.core.overall.OverallVariable;
import com.dcloud.core.source.ISource;
import com.dcloud.core.source.RdbmsSource;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @Description 关系型数据库客户端
 * @Author shijf
 * @Date 0:18 2022/7/7
 */
@Slf4j
public abstract class AbsRdbmsClient<T> implements IClient<Map<String, Object>> {
    private AbsRdbmsConnFactory connFactory = getConnFactory();

    // 字段重复时的重命名规则
    private static final String REPEAT_SIGN = "%s(%s)";

    /**
     * 获取连接工厂 静态方法的静态方法，由这个类的继承子类来实现
     *
     * @return
     */
    protected abstract AbsRdbmsConnFactory getConnFactory();


    @Override
    public Connection getCon(ISource source) {
        return getCon(source, null);
    }

    @Override
    public Connection getCon(ISource source, String taskParams) {
        log.info("-------getting connection....-----");
        try {
            return connFactory.getConn(source, taskParams);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public Boolean testCon(ISource source) {
        return connFactory.testConn(source);
    }

    @Override
    public List<Map<String, Object>> execQuery(ISource iSource, String taskSql) throws DtLoaderException{
        Connection conn = getConnection((RdbmsSource) iSource);

        List<Map<String, Object>> result = new LinkedList<>();
        ResultSet res = null;
        Statement statement = null;
        Integer queryTimeout = null;
        try {
            statement = conn.createStatement();
            if (queryTimeout != null) {
                try {
                    statement.setQueryTimeout(queryTimeout);
                } catch (Exception e) {
                    log.debug(String.format("statement set QueryTimeout exception,%s", e.getMessage()), e);
                }
            }

            if (statement.execute(taskSql)) {
                res = statement.getResultSet();
                int columns = res.getMetaData().getColumnCount();
                List<String> columnName = new LinkedList<>();
                for (int i = 0; i < columns; i++) {
                    columnName.add(res.getMetaData().getColumnLabel(i + 1));
                }

                while (res.next()) {
                    Map<String, Object> row = new LinkedHashMap<>();
                    Map<String, Integer> columnRepeatSign = new LinkedHashMap<>();
                    for (int i = 0; i < columns; i++) {
                        String column = dealRepeatColumn(row, columnName.get(i), columnRepeatSign);
                        Object value = res.getObject(i + 1);
                        row.put(column, value);
                    }
                    result.add(row);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
            //throw new DtLoaderException(String.format("SQL execute exception：%s", e.getMessage()), e);
        } finally {
            this.closeDBResources(res, statement, null);
        }
        return result;
    }

    /**
     * @Description 根据数据源连接
     *              会查询链接是否在全局变量中是否存在数据源连接，如果存在就从全局变量中获取，如果不存在就建立链接并且加入到全局缓存中
     * @param iSource
     * @return java.sql.Connection
     * @Author shijf
     * @Date 14:49 2022/8/8
     */
    private Connection getConnection(RdbmsSource iSource) {

        RdbmsSource defineSource = iSource;
        Connection conn = null;
        if(defineSource.getConnection() == null){
            //查询全局静态变量中是否存在
            Object vByCode = OverallVariable.getVByCode(defineSource.getUrl());
            if(vByCode == null){
                Connection con = this.getCon(defineSource, null);
                defineSource.setConnection(con);
                OverallVariable.setVByCode(defineSource.getUrl(),con);
                conn = con;
            }else{
                conn = (Connection)vByCode;
                try {
                    if(conn.isClosed()){
                        Connection con = this.getCon(defineSource, null);
                        defineSource.setConnection(con);
                        OverallVariable.setVByCode(defineSource.getUrl(),con);
                        conn = con;
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }else{
            conn = defineSource.getConnection();
        }
        return conn;
    }

    @Override
    public Long execQueryCount(ISource defineSource, String taskSql) {
        //taskSql 增强
        String countSql = "select count(1) as `count` from ( "+taskSql+" ) a ";
        List<Map<String, Object>> maps = this.execQuery(defineSource, countSql);
        return (Long) maps.get(0).get("count");
    }

    @Override
    public List getAllTable(RdbmsSource defineSource) {
        return null;
    }



    /**
     * 处理 executeQuery 查询结果字段重复字段
     *
     * @param row              当前行的数据
     * @param column           当前查询字段名
     * @param columnRepeatSign 当前字段重复次数
     * @return 处理后的重复字段名
     */
    public static String dealRepeatColumn(Map<String, Object> row, String column, Map<String, Integer> columnRepeatSign) {
        boolean repeat = row.containsKey(column);
        if (repeat) {
            // 如果 column 重复则在 column 后进行增加 (1),(2)... 区分处理
            boolean contains = columnRepeatSign.containsKey(column);
            if (!contains) {
                columnRepeatSign.put(column, 1);
            } else {
                columnRepeatSign.put(column, columnRepeatSign.get(column) + 1);
            }
            return String.format(REPEAT_SIGN, column, columnRepeatSign.get(column));
        } else {
            return column;
        }
    }

    /**
     * 关闭数据库资源信息
     *
     * @param rs
     * @param stmt
     * @param conn
     */
    public static void closeDBResources(ResultSet rs, Statement stmt, Connection conn) {
        if (null != rs) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
        }


        if (null != stmt) {
            try {
                stmt.close();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
        }

        if (null != conn) {
            try {
                conn.close();
            } catch (SQLException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    @Override
    public Boolean createTable( ISource source, String createTableSql) {
        Connection conn = getConnection((RdbmsSource) source);

        List<Map<String, Object>> result = new LinkedList<>();
        ResultSet res = null;
        Statement statement = null;
        Integer queryTimeout = null;
        try {
            statement = conn.createStatement();
            if (queryTimeout != null) {
                try {
                    statement.setQueryTimeout(queryTimeout);
                } catch (Exception e) {
                    log.debug(String.format("statement set QueryTimeout exception,%s", e.getMessage()), e);
                }
            }

            if (statement.execute(createTableSql)) {
                res = statement.getResultSet();
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
            //throw new DtLoaderException(String.format("SQL execute exception：%s", e.getMessage()), e);
        } finally {
            this.closeDBResources(res, statement, null);
        }
        return true;
    }
}
